// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author		: HiDark 1173296519@qq.com
// File			: psram.sv
// Create		: 2024-5-18 
// Description	: IS66/67WVS4M8ALL/BLL behavioral model
// Editor		: tab size (4)
// -----------------------------------------------------------------------------

module psram(
    input sck,
    input ce_n,
    inout [3:0] dio
  );
localparam  SIZE         = 4 * 1024 * 1024;
localparam  CMD_QPI      = 8'h35;
localparam  CMD_QREAD    = 8'hEB;
localparam  CMD_QWRITE   = 8'h38;

// State  
localparam  CMD          = 3'h0,
            QPI          = 3'h1,
            ADDR         = 3'h2,
            WAIT         = 3'h3,
            QWRITE       = 3'h4,
            QREAD        = 3'h5,
            ERROR        = 3'h6;
// 32Mb memory array
reg [7:0] memory [0:SIZE-1];
// signals
reg     [2:0]   state;
reg     [7:0]   counter;
reg     [7:0]   cmd;
reg             qpi_flag;
reg     [23:0]  addr_base;
reg     [3:0]   rdata;
wire    [21:0]  addr;
reg     [3:0]   wdata_r;
wire    [3:0]   wdata;
wire            reset  ;
wire            read_en; // for tri-state gate
wire            byte_order ;// high->7:4, low->3:0

assign wdata        = dio;
assign reset        = ce_n;
assign read_en      = (state == QREAD); // for tri-state gate
assign byte_order   = ~counter[0];// high->7:4, low->3:0

always@(posedge sck or posedge reset) begin
    if (reset && !qpi_flag)
        qpi_flag <= 1'b0;// only for simulation
    else if ({ cmd[6:0], dio[0] } == CMD_QPI)
        qpi_flag <= 1'b1;        
end

always@(posedge sck or posedge reset) begin
    if (reset) 
        state <= qpi_flag?QPI:CMD;
    else begin
        case (state)
        CMD  :  state <= (counter == 8'd7 ) ? ADDR : state;
        QPI  :  state <= (counter == 8'd1 ) ? ADDR : state;        
        ADDR :  state <= (counter == 8'd5 ) ? (
                        (cmd     == CMD_QREAD )? WAIT  :
                        (cmd     == CMD_QWRITE)? QWRITE:ERROR):state;
        WAIT :  state <= (counter == 8'd5 ) ? QREAD: state  ;
        QREAD,QWRITE : state <= state;
        default: begin // CMD ERROR
            state <= state;
            $display("Assertion failed: Unsupported command `%xh`, only support `EBh` Qread or `38h` Qwrite command", cmd);
            $fatal;
        end
        endcase
    end
end

always@(posedge sck or posedge reset) begin
    if (reset) 
        counter <= 8'd0;
    else begin
        case (state)
        CMD:   counter <= (counter < 8'd7 ) ? counter + 8'd1 : 8'd0;
        QPI:   counter <= (counter < 8'd1 ) ? counter + 8'd1 : 8'd0;        
        ADDR:  counter <= (counter < 8'd5 ) ? counter + 8'd1 : 8'd0;
        WAIT:  counter <= (counter < 8'd5 ) ? counter + 8'd1 : 8'd0; 
        QWRITE,QREAD:  counter <= counter + 8'd1;             
        default: counter <= counter + 8'd1;
        endcase
    end
end

always@(posedge sck or posedge reset) begin
    if (reset)               
        cmd <= 8'd0;
    else if (state == CMD) 
        cmd <= { cmd[6:0], dio[0] };
    else if (state == QPI)
        cmd <= { cmd[3:0], dio[3:0] };        
end

always@(posedge sck or posedge reset) begin
    if (reset) 
        addr_base <= 24'd0;
    else if (state == ADDR)
        addr_base <= { addr_base[19:0], dio[3:0] };
end

assign addr = addr_base[21:0] + {13'b0,counter>>1};


always@(posedge sck or posedge reset) begin
    if (state == QWRITE) begin
        if (byte_order == 1'b0)
            memory[addr] <= {wdata_r,wdata};
        else
            wdata_r      <= wdata;            
    end
end
always@(negedge sck or posedge reset) begin
    if (state == QREAD)
        rdata <= byte_order?(memory[addr][7:4]):(memory[addr][3:0]);
end

assign dio[0] = read_en ? rdata[0] : 1'bz ;
assign dio[1] = read_en ? rdata[1] : 1'bz ;
assign dio[2] = read_en ? rdata[2] : 1'bz ;
assign dio[3] = read_en ? rdata[3] : 1'bz ;

endmodule
  
